package cn.yeziji.forum.filter;

import cn.yeziji.forum.common.FilterToken;
import cn.yeziji.forum.common.enums.ForumCommonStatus;
import cn.yeziji.forum.common.ForumResult;
import cn.yeziji.forum.common.key.UserCacheKey;
import cn.yeziji.forum.exception.TokenException;
import cn.yeziji.forum.common.enums.status.TokenStatus;
import cn.yeziji.forum.utils.CacheUtils;
import cn.yeziji.forum.utils.JwtOperaUtils;
import cn.yeziji.forum.utils.ResponseUtils;
import cn.yeziji.forum.utils.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Objects;

/**
 * jwt 权限过滤器
 *
 * @author gzkemays
 * @since 2021/12/21 16:43
 */
@Slf4j
public class JwtAuthFilter extends BasicAuthenticationFilter {
  CacheUtils cache = SpringUtils.getBean(CacheUtils.class);

  public JwtAuthFilter(AuthenticationManager authenticationManager) {
    super(authenticationManager);
  }

  /**
   * 过滤操作
   *
   * @param request 请求
   * @param response 响应
   * @param chain 过滤体
   * @throws IOException io 异常
   * @throws ServletException servlet 异常
   */
  @Override
  protected void doFilterInternal(
      HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    String token = request.getHeader(HttpHeaders.AUTHORIZATION);
    if (Objects.nonNull(token)) {
      // 如果使用的 token 没有被记录于缓存当中，那么被认定为无效 token。[刷新的 token 会替换原来的 token]
      Object obj = cache.get(UserCacheKey.GENERATE.getTokenCacheKey(token));
      if (obj == null) {
        ResponseUtils.writeJson(
            response, ForumResult.buildByException(new TokenException(TokenStatus.TOKEN_INVALID)));
        return;
      }
      // 获取用户权限以及刷新 token
      FilterToken authentication = getAuthentication(token);
      // 如果 token == null 时 authentication 才可能为 null
      assert authentication != null;
      String refreshToken = authentication.getRefreshToken();
      // 如果刷新了 token 那么将 token 塞入响应头当中
      if (StringUtils.isNotBlank(refreshToken)) {
        response.setHeader(HttpHeaders.AUTHORIZATION, refreshToken);
      }
      SecurityContextHolder.getContext().setAuthentication(authentication.getAuthenticationToken());
      chain.doFilter(request, response);
    } else {
      log.info("访问 ---> {}", request.getRequestURI());
      ResponseUtils.writeJson(
          response, ForumResult.buildByResultEnums(ForumCommonStatus.LOGIN_NULL));
    }
  }

  /**
   * 鉴权失败操作
   *
   * @param request 请求
   * @param response 响应
   * @param failed 失败异常
   * @throws IOException io 操作异常
   */
  @Override
  protected void onUnsuccessfulAuthentication(
      HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
      throws IOException {
    super.onUnsuccessfulAuthentication(request, response, failed);
  }

  private FilterToken getAuthentication(String token) {
    if (Objects.nonNull(token)) {
      FilterToken filterToken = new FilterToken();
      String username = JwtOperaUtils.getUserNameByToken(token);
      List<SimpleGrantedAuthority> userRoles;
      String refreshToken;
      // 判断是否生成了 refresh token
      if ((refreshToken = cache.get(UserCacheKey.GENERATE.getRefreshTokenKey(token))) != null) {
        filterToken.setRefreshToken(refreshToken);
        userRoles = JwtOperaUtils.getUserRole(refreshToken);
      } else {
        userRoles = JwtOperaUtils.getUserRole(token);
      }
      filterToken.setAuthenticationToken(
          new UsernamePasswordAuthenticationToken(username, null, userRoles));
      return filterToken;
    }
    return null;
  }
}
